Transparent, fully reproducible rebuild of the MRD–PET/CT agreement and prognosis analysis using paired 2×2 data. All inputs are harmonised to canonical cell counts (a = MRD−/PET+, b = MRD−/PET−, c = MRD+/PET−, d = MRD+/PET+) with percentages back-calculated to counts by proportional allocation and reconciled to the reported cohort size (n), where needed.
Analysis sets: (i) Primary (MRD and PET/CT assessed at the same time point); (ii) Strict (Δ ≤ 30 days between MRD and PET/CT); (iii) Sensitivity (including reconstructed rows and prespecified exclusions). Agreement metrics: Per study we report the pooled concordant proportion (b+d)/n, Cohen’s κ (standard error and 95% confidence interval), McNemar mid-p, and Gwet’s AC1 and prevalence- and bias-adjusted κ (PABAK). Study-level κ is pooled using random-effects (restricted maximum likelihood) with Knapp–Hartung adjustment. Directional discordance is defined as log-odds(MRD−/PET+ vs MRD+/PET−) with Haldane–Anscombe correction and synthesised by random-effects meta-analysis; 95% prediction intervals are reported when estimable.
Fréchet–Hoeffding κ bounds: Identification regions are derived per study and for pooled marginals. In this build, bounds are presented graphically (Supplementary Methods/figure), whereas the corresponding summary table may be omitted if inputs are insufficient for tabulation; bounds frequently cross zero, indicating weak identifiability of κ and supporting cautious interpretation and a complementary view of MRD and PET/CT.
Survival (progression-free survival, PFS): Dual-negative (MRD−/PET−) versus all other response categories. Hazard ratios (HR) are pooled using random-effects (restricted maximum likelihood) with Knapp–Hartung, yielding a pooled HR of 0.33 (95% CI 0.24–0.45) favouring dual-negative versus others. Small-study asymmetry tests are interpreted descriptively given the limited number of cohorts.
Primary pooled 2×2 matrix (same-time-point set): MRD−/PET− 32.1% (n=374), MRD−/PET+ 37.4% (n=435), MRD+/PET− 26.1% (n=304), MRD+/PET+ 4.4% (n=51); analyzable paired n=1,164. Pooled concordant proportion = 36.5% and pooled κ = −0.26 (95% CI −0.31 to −0.21).
Key outputs: Table 1 (study characteristics); Table 2 (concordance and directional discordance); Figure 1 (PRISMA diagram); Figure 2A (pooled κ forest plot); Figure 2B (directional discordance meta-analysis); Figure 3A (PFS meta-analysis); Figure 3B (funnel plot); Supplementary Figure SF1 (pooled 2×2 matrix); Supplementary Tables ST1–ST2 (Fréchet–Hoeffding bounds and risk-of-bias). Optional supplementary outputs (simulation and decision-curve analysis) are omitted when required inputs are unavailable.
*Figure S1 omitted: simulation unavailable.**Figure S2 omitted: DCA inputs unavailable.*
*Table S1 omitted: could not compute FH bounds.*
ST1 source: tab2 (in-memory); n=10; cols = Study, n, MRD-/PET+, MRD-/PET-, MRD+/PET-, MRD+/PET+, pct_agree, Kappa, McNemar_p, AC1, AC1_se, AC1_lcl, AC1_ucl, PABAK, PABAK_lcl, PABAK_ucl
.
Supplementary Methods S3 – κ identification intervals (Fréchet–Hoeffding bounds) with point estimate
agg_png
2
B. Directional discordance (PRIMARY) — Lancet style
Figure 2B (STRICT; Δ ≤ 30) — Directional discordance
Supplementary Figure SF1. Concordance matrices for PRIMARY and STRICT time-points. STRICT applies the Δ(MRD–PET) ≤ 30 days rule to treat assessments as paired.
<em>Strict analysis:</em> pooled log-odds = -0.67 (-2.02 – 0.68); I² = 94%
</div>
| Study | logHR | SE | HR | HR_lo | HR_hi |
|---|---|---|---|---|---|
| Alonso R, Cedena MT, Gomez-Grande A, Rios R, Moraleda JM, Cabanas V, Moreno MJ, Lopez-Jimenez J, Martin F, Sanz A, Valeri A, Jimenez A, Sanchez R, Lahuerta JJ, Martinez-Lopez J | -1.509 | 0.237 | 0.221 | 0.139 | 0.352 |
| Moreau, P et al, 2019; Kraeber et al 2025 | -0.935 | 0.273 | 0.393 | 0.230 | 0.670 |
| Zamagni E, Oliva S, Gay F, Capra A, Rota-Scalabrini D, D’Agostino M, Belotti A, Galli M, Racca M, Zambello R, Gamberi B, Albano D, Bertamini L, Versari A, Grasso M, Sgherza N, Priola C, Fioritoni F, Patriarca F, De Cicco G, Villanova T, Pascarella A, Zucchetta P, Tacchetti P, Fanti S, Mancuso K, Barbato S, Boccadoro M, Musto P, Cavo M, Nanni C | -0.799 | 0.342 | 0.450 | 0.230 | 0.880 |
| Mookerjee A, Gupta R, Kumar R, Sharma A, Pandey RM, Kumar L | -1.151 | 0.234 | 0.316 | 0.200 | 0.500 |
| Fonseca R, Arribas M, Wiedmeier-Nutor JE, et al. | -0.564 | 0.485 | 0.569 | 0.220 | 1.470 |
Figure 3. Dual-negative MRD−/PET-CT− versus all other states: pooled hazard ratio for progression-free survival
| PFS pooled summary (dual-negative vs all others) | |||||
| Random-effects REML with Knapp–Hartung. Values < 1 favor dual-negative. | |||||
| Model | k | HR (95% CI) | Prediction interval (HR) | τ² | I² (%) |
|---|---|---|---|---|---|
| Small-study asymmetry tests have low power when k < 10; interpret cautiously. | |||||
stopifnot(exists("surv_df"), nrow(surv_df) >= 1)
if (!exists("res_hr") || !inherits(res_hr, "rma.uni")) {
res_hr <- metafor::rma(yi = surv_df$logHR, vi = surv_df$vi, method = "REML", test = "knha")
}
mu <- as.numeric(res_hr$b)
egger_p <- NA_real_
if (nrow(surv_df) >= 3) {
eg <- metafor::regtest(res_hr, model = "rma")
egger_p <- eg$pval
}
egger_lab <- if (is.finite(egger_p)) sprintf("Egger p = %.3f", egger_p) else "Egger p = NA"
# --------------------------------------------
# Limits from data to include ALL studies
x_min <- min(surv_df$logHR, na.rm = TRUE) - 0.15
x_max <- max(surv_df$logHR, na.rm = TRUE) + 0.15
y_max <- max(surv_df$SE, na.rm = TRUE) + 0.02
y_seq <- seq(0, y_max, length.out = 200)
cone_left <- data.frame(x = mu - 1.96*y_seq, y = y_seq)
cone_right <- data.frame(x = mu + 1.96*y_seq, y = y_seq)
p_fun <- ggplot(surv_df, aes(x = logHR, y = SE)) +
geom_line(data = cone_left, aes(x, y), linetype = "dashed") +
geom_line(data = cone_right, aes(x, y), linetype = "dashed") +
geom_vline(xintercept = mu, linetype = "dashed") +
geom_point(size = 2.5) +
ggrepel::geom_label_repel(aes(label = StudyShort),
size = 3, label.size = 0.25,
label.r = grid::unit(0.08, "lines"), # <- change unit() -> grid::unit()
min.segment.length = 0, seed = 1234) +
coord_cartesian(xlim = c(x_min, x_max), expand = FALSE) +
labs(x = "log(HR)", y = "SE", title = "Funnel plot (log(HR) vs SE)") +
annotate("text", x = x_max, y = y_max, hjust = 1.1, vjust = -0.5, label = egger_lab, size = 3) +
theme_minimal(base_size = 11) +
theme(panel.grid.minor = element_blank(),
plot.title = element_text(hjust = 0.5))
out_png <- here::here("figs","Figure_3B_Funnel.png")
ggsave(out_png, p_fun, width = 7.8, height = 5.0, dpi = 300, bg = "white")
cap <- if (exists("legend_for")) legend_for("Figure_3B_Funnel.png") else
"B. Funnel plot (log(HR) vs SE). Dashed cone = ~95% pseudo-CIs; vertical line = pooled effect; Egger’s p shown; for k<10 interpret with caution."
if (file.exists(out_png)) { if (exists("show_png")) show_png("figs", basename(out_png), caption = cap, width = "95%") else knitr::include_graphics(out_png) }
B. Funnel plot (log(HR) vs SE). Dashed cone = ~95% pseudo-CIs; vertical line = pooled effect. Egger’s test p shown; for k<10 interpret with caution.
| ST2: Egger summary — PFS HR (dual−negative vs others) | |||
| model | k | center_log | Egger_p |
|---|---|---|---|
| Leave-one-out (sorted by |Δ pooled|) | ||||||||||
| Study | Center | LOO_est | LOO_se | Delta | LCL | UCL | HR_center | HR | HR_LCL | HR_UCL |
|---|---|---|---|---|---|---|---|---|---|---|
Baujat plot
x y ids slab
1 1.99245249 1.44781779 1 1
2 0.23620252 0.02205887 2 2
3 0.58403678 0.10144991 3 3
4 0.04032006 0.06436193 4 4
5 1.05091602 0.12297498 5 5
#QUIPS / QUADAS-2: setup, templates, and import
Sensitivity (exclude QUADAS-2 High): κ = 0.163 (0.103–0.224)
NULL
[1] “figs/Supplementary_Figure_QUADAS2_Traffic.png” attr(,“class”) [1]
“knit_image_paths” “knit_asis”
QUIPS overall unavailable or only one level → subgroup meta
skipped.
Clinical pathway schematic mapping 2×2 states to suggested actions.
Supplementary Figures (ZIP) (5.1 MB) Supplementary Tables (ZIP) (55.9 KB) Complete Supplementary Bundle (ZIP) (5.2 MB)
sessionInfo()
R version 4.5.2 (2025-10-31)
Platform: x86_64-apple-darwin20
Running under: macOS Sequoia 15.4.1
Matrix products: default
BLAS: /Library/Frameworks/R.framework/Versions/4.5-x86_64/Resources/lib/libRblas.0.dylib
LAPACK: /Library/Frameworks/R.framework/Versions/4.5-x86_64/Resources/lib/libRlapack.dylib; LAPACK version 3.12.1
locale:
[1] en_US.UTF-8/en_US.UTF-8/en_US.UTF-8/C/en_US.UTF-8/en_US.UTF-8
time zone: Europe/Zurich
tzcode source: internal
attached base packages:
[1] grid stats graphics grDevices utils datasets methods base
other attached packages:
[1] fs_1.6.6 patchwork_1.3.2 knitr_1.51 rlang_1.1.6 gt_1.2.0
[6] metasens_1.5-3 rsvg_2.7.0 DiagrammeRsvg_0.1 DiagrammeR_1.0.11 here_1.0.2
[11] ggforce_0.5.0 RColorBrewer_1.1-3 htmltools_0.5.9 DT_0.34.0 zip_2.3.3
[16] htmlwidgets_1.6.4 DescTools_0.99.60 writexl_1.5.4 glue_1.8.0 scales_1.4.0
[21] ggrepel_0.9.6 png_0.1-8 cowplot_1.2.0 broom_1.0.11 janitor_2.2.1
[26] irr_0.84.1 lpSolve_5.6.23 meta_8.2-1 metafor_4.8-0 numDeriv_2016.8-1.1
[31] metadat_1.4-0 Matrix_1.7-4 readxl_1.4.5 lubridate_1.9.4 forcats_1.0.1
[36] stringr_1.6.0 dplyr_1.1.4 purrr_1.2.0 readr_2.1.6 tidyr_1.3.2
[41] tibble_3.3.0 ggplot2_4.0.1 tidyverse_2.0.0
loaded via a namespace (and not attached):
[1] mathjaxr_2.0-0 rstudioapi_0.17.1 jsonlite_2.0.0 magrittr_2.0.4 magick_2.9.0 farver_2.1.2
[7] nloptr_2.2.1 rmarkdown_2.30 ragg_1.5.0 vctrs_0.6.5 minqa_1.2.8 CompQuadForm_1.4.4
[13] curl_7.0.0 haven_2.5.5 cellranger_1.1.0 sass_0.4.10 bslib_0.9.0 rootSolve_1.8.2.4
[19] cachem_1.1.0 commonmark_2.0.0 lifecycle_1.0.4 pkgconfig_2.0.3 R6_2.6.1 fastmap_1.2.0
[25] rbibutils_2.4 snakecase_0.11.1 digest_0.6.39 Exact_3.3 rprojroot_2.1.1 textshaping_1.0.4
[31] crosstalk_1.2.2 labeling_0.4.3 timechange_0.3.0 httr_1.4.7 polyclip_1.10-7 compiler_4.5.2
[37] proxy_0.4-28 bit64_4.6.0-1 withr_3.0.2 S7_0.2.1 backports_1.5.0 MASS_7.3-65
[43] gtools_3.9.5 gld_2.6.8 tools_4.5.2 otel_0.2.0 nlme_3.1-168 generics_0.1.4
[49] gtable_0.3.6 tzdb_0.5.0 class_7.3-23 data.table_1.18.0 lmom_3.2 hms_1.1.4
[55] xml2_1.5.1 pillar_1.11.1 markdown_2.0 vroom_1.6.7 splines_4.5.2 tweenr_2.0.3
[61] lattice_0.22-7 bit_4.6.0 tidyselect_1.2.1 juicyjuice_0.1.0 reformulas_0.4.3 V8_8.0.1
[67] litedown_0.9 xfun_0.55 expm_1.0-0 visNetwork_2.1.4 stringi_1.8.7 yaml_2.3.12
[73] boot_1.3-32 evaluate_1.0.5 robvis_0.3.0 cli_3.6.5 systemfonts_1.3.1 Rdpack_2.6.4
[79] jquerylib_0.1.4 dichromat_2.0-0.1 Rcpp_1.1.0 parallel_4.5.2 lme4_1.1-38 mvtnorm_1.3-3
[85] e1071_1.7-17 crayon_1.5.3